package mgo

import (
	"context"
	"fmt"
	"github.com/pkg/errors"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type OperationInfo struct {
	Collection *mongo.Collection // 操作集合，建议每个集合初始化的时候都调用 BuildCollection() 方法生成一个对应集合的全局操作集合变量
	Id         string            // 指定 ID，FindOne/FindOneWithResult/UpdateByID/DeleteOne
	Filter     *Filter           // 查找\修改\删除条件
	Document   interface{}       // 插入单条数据或修改时文档内容, InsertOne/UpdateByID/ReplaceOne/FindOneAndReplace
	Update     *UpdateInfo       // 修改内容，UpdateByID/UpdateOne/UpdateMany/FindOneAndUpdate
	Documents  []interface{}     // 插入多条数据的文档内容，InsertMany
	FieldName  string            // 指定字段，用于 Distinct 方法
	Result     interface{}       // 查询时，指定接收结果的容器指针，FindOneWithResult/FindWithResult/FindOneAndUpdate/FindOneAndDelete/FindOneAndReplace
	Ctx        context.Context   // 注入操作上下文，如果未传入，默认建立 context.Background()
	OptionInfo *OptionInfo       // 常用快捷的 Option 设置，如果设置了此项，忽略系统 option 设置内容
	// 系统 options 设置
	InsertOneOptions         *options.InsertOneOptions
	InsertManyOptions        *options.InsertManyOptions
	FindOptions              *options.FindOptions
	FindOneOptions           *options.FindOneOptions
	UpdateOptions            *options.UpdateOptions
	DeleteOptions            *options.DeleteOptions
	ReplaceOptions           *options.ReplaceOptions
	FindOneAndUpdateOptions  *options.FindOneAndUpdateOptions
	FindOneAndDeleteOptions  *options.FindOneAndDeleteOptions
	FindOneAndReplaceOptions *options.FindOneAndReplaceOptions
	DistinctOptions          *options.DistinctOptions
	CountOptions             *options.CountOptions
	EstimatedCountOptions    *options.EstimatedDocumentCountOptions

	// 原生条件和修改信息
	OriginFilter interface{} // 如果直接注入原生条件,则不再通过当前包构造任何条件
	OriginUpdate interface{} // 如果直接注入修改内容,则不再通过当前包构造修改信息
	// 初始化信息
	objectId primitive.ObjectID
	filter   interface{}
	update   interface{}
}

// 初始化操作
func initOperation(o *OperationInfo) (err error) {
	if o == nil {
		return errors.New("mgo operation info cannot be nil")
	}
	if o.Collection == nil {
		return errors.New("mgo collection cannot be nil")
	}
	if o.Ctx == nil {
		o.Ctx = context.Background()
	}
	if o.Id != "" {
		o.objectId, err = primitive.ObjectIDFromHex(o.Id)
		if err != nil {
			return err
		}
		o.Filter = &Filter{Id: o.objectId}
	}
	if o.OriginFilter != nil {
		o.filter = o.OriginFilter
	} else if o.Filter != nil {
		o.filter = buildFilter(o.Filter)
	}
	if o.OriginUpdate != nil {
		o.update = o.OriginUpdate
	} else if o.Update != nil {
		o.update = o.Update.BuildUpdate()
	}
	fmt.Println(o.filter)
	return nil
}

// InsertOne 插入单条数据
func InsertOne(info *OperationInfo) (string, error) {
	if err := initOperation(info); err != nil {
		return "", err
	}
	if info.Document == nil {
		return "", errors.New("mgo insertOne：document cannot be nil")
	}
	res, err := info.Collection.InsertOne(info.Ctx, info.Document, info.InsertOneOptions)
	if err != nil {
		return "", err
	}
	return res.InsertedID.(primitive.ObjectID).Hex(), nil
}

// InsertMany 插入多条数据
func InsertMany(info *OperationInfo) (int, error) {
	if err := initOperation(info); err != nil {
		return 0, err
	}
	if len(info.Documents) == 0 {
		return 0, errors.New("mgo insertMany：documents cannot be nil")
	}
	res, err := info.Collection.InsertMany(info.Ctx, info.Documents, info.InsertManyOptions)
	if err != nil {
		return 0, err
	}
	return len(res.InsertedIDs), nil
}

// FindOne 查询单条数据
func FindOne(info *OperationInfo) (*mongo.SingleResult, error) {
	if err := initOperation(info); err != nil {
		return nil, err
	}
	if info.OptionInfo != nil {
		info.FindOneOptions = info.OptionInfo.BuildFindOneOptions()
	}
	return info.Collection.FindOne(info.Ctx, info.filter, info.FindOneOptions), nil
}

// FindOneWithResult 查询单挑数据并映射到结果指针中
func FindOneWithResult(info *OperationInfo) (bool, error) {
	if info.Result == nil {
		return false, errors.New("mgo findOneWithResult：result box cannot be empty")
	}
	res, err := FindOne(info)
	if err != nil {
		return false, err
	}
	if err := res.Decode(info.Result); err == mongo.ErrNoDocuments {
		return false, nil
	} else if err != nil {
		return false, err
	}
	return true, nil
}

// Find 查询多条数据
func Find(info *OperationInfo) (*mongo.Cursor, error) {
	if err := initOperation(info); err != nil {
		return nil, err
	}
	if info.OptionInfo != nil {
		info.FindOptions = info.OptionInfo.BuildFindOptions()
	} else {
		info.FindOptions = options.Find()
	}
	if info.FindOptions.Limit == nil {
		info.FindOptions = info.FindOptions.SetLimit(100) // 无查询数量限制时,限制返回条数为 100
	}
	return info.Collection.Find(info.Ctx, info.filter, info.FindOptions)
}

// FindWithResult 查询多条数据，并映射到结果指针中
func FindWithResult(info *OperationInfo) (int, error) {
	if info.Result == nil {
		return 0, errors.New("mgo findWithResult：result box cannot be empty")
	}
	cur, err := Find(info)
	if err != nil {
		return 0, err
	}
	if err := cur.All(info.Ctx, info.Result); err != nil {
		return 0, err
	}
	return cur.RemainingBatchLength(), nil
}

// UpdateByID 通过 ID 修改数据
func UpdateByID(info *OperationInfo) (*mongo.UpdateResult, error) {
	if err := initOperation(info); err != nil {
		return nil, err
	}
	if info.update == nil {
		return nil, errors.New("mgo updateByID：update cannot be nil")
	}
	return info.Collection.UpdateByID(info.Ctx, info.objectId, info.update, info.UpdateOptions)
}

// UpdateOne 根据条件修改单条数据
func UpdateOne(info *OperationInfo) (*mongo.UpdateResult, error) {
	if err := initOperation(info); err != nil {
		return nil, err
	}
	if info.filter == nil || info.update == nil {
		return nil, errors.New("mgo UpdateOne：filter/update cannot be nil")
	}
	return info.Collection.UpdateOne(info.Ctx, info.filter, info.update, info.UpdateOptions)
}

// updateMany 根据条件修改多条数据
func updateMany(info *OperationInfo) (*mongo.UpdateResult, error) {
	if err := initOperation(info); err != nil {
		return nil, err
	}
	if info.filter == nil || info.update == nil {
		return nil, errors.New("mgo updateMany：filter/update cannot be nil")
	}
	return info.Collection.UpdateMany(info.Ctx, info.filter, info.update, info.UpdateOptions)
}

// FindOneAndUpdate 修改指定条件的单条文档内容，并返回之前的文档数据
func FindOneAndUpdate(info *OperationInfo) error {
	if err := initOperation(info); err != nil {
		return err
	}
	if info.filter == nil || info.update == nil || info.Result == nil {
		return errors.New("mgo findOneAndUpdate：filter/document/result cannot be nil")
	}
	if info.OptionInfo != nil {
		info.FindOneAndUpdateOptions = info.OptionInfo.BuildFindOneAndUpdateOptions()
	}
	res := info.Collection.FindOneAndUpdate(info.Ctx, info.filter, info.update, info.FindOneAndUpdateOptions)
	if res.Err() != nil {
		return res.Err()
	}
	return res.Decode(info.Result)
}

// DeleteOne 删除单条数据
func DeleteOne(info *OperationInfo) (int64, error) {
	if err := initOperation(info); err != nil {
		return 0, err
	}
	if info.filter == nil {
		return 0, errors.New("mgo deleteOne：filter cannot be nil")
	}
	res, err := info.Collection.DeleteOne(info.Ctx, info.filter, info.DeleteOptions)
	if err != nil {
		return 0, err
	}
	return res.DeletedCount, nil
}

// 删除多条数据
func deleteMany(info *OperationInfo) (int64, error) {
	if err := initOperation(info); err != nil {
		return 0, err
	}
	if info.filter == nil {
		return 0, errors.New("mgo deleteMany：filter cannot be nil")
	}
	res, err := info.Collection.DeleteMany(info.Ctx, info.filter, info.DeleteOptions)
	if err != nil {
		return 0, err
	}
	return res.DeletedCount, nil
}

// FindOneAndDelete 删除指定条件的单条文档内容，并返回之前的文档数据
func FindOneAndDelete(info *OperationInfo) error {
	if err := initOperation(info); err != nil {
		return err
	}
	if info.filter == nil || info.Result == nil {
		return errors.New("mgo findOneAndDelete：filter/result cannot be nil")
	}
	if info.OptionInfo != nil {
		info.FindOneAndDeleteOptions = info.OptionInfo.BuildFindOneAndDeleteOptions()
	}
	res := info.Collection.FindOneAndDelete(info.Ctx, info.filter, info.FindOneAndDeleteOptions)
	if res.Err() != nil {
		return res.Err()
	}
	return res.Decode(info.Result)
}

// ReplaceOne 替换指定条件的单条文档内容
func ReplaceOne(info *OperationInfo) (*mongo.UpdateResult, error) {
	if err := initOperation(info); err != nil {
		return nil, err
	}
	if info.filter == nil || info.Document == nil {
		return nil, errors.New("mgo replaceOne：filter/document cannot be nil")
	}
	return info.Collection.ReplaceOne(info.Ctx, info.filter, info.Document, info.ReplaceOptions)
}

// FindOneAndReplace 替换指定条件的单条文档内容，并返回之前的文档数据
func FindOneAndReplace(info *OperationInfo) error {
	if err := initOperation(info); err != nil {
		return err
	}
	if info.filter == nil || info.Document == nil || info.Result == nil {
		return errors.New("mgo findOneAndReplace：filter/document/result cannot be nil")
	}
	if info.OptionInfo != nil {
		info.FindOneAndReplaceOptions = info.OptionInfo.BuildFindOneAndReplaceOptions()
	}
	res := info.Collection.FindOneAndReplace(info.Ctx, info.filter, info.Document, info.FindOneAndReplaceOptions)
	if res.Err() != nil {
		return res.Err()
	}
	return res.Decode(info.Result)
}

// Distinct 根据指定字段去重后查询结果
func Distinct(info *OperationInfo) ([]interface{}, error) {
	if err := initOperation(info); err != nil {
		return nil, err
	}
	if info.filter == nil || info.Result == nil {
		return nil, errors.New("mgo Distinct：filter/result cannot be nil")
	}
	if info.OptionInfo != nil {
		info.DistinctOptions = info.OptionInfo.BuildDistinctOptions()
	}
	return info.Collection.Distinct(info.Ctx, info.FieldName, info.filter, info.DistinctOptions)
}

// Count 根据条件查询集合中的文档数量，必须存在查询条件，查询全部数量条件传 bson.D{}
func Count(info *OperationInfo) (int64, error) {
	if err := initOperation(info); err != nil {
		return 0, err
	}
	if info.filter == nil {
		return 0, errors.New("mgo count：filter cannot be nil")
	}
	return info.Collection.CountDocuments(info.Ctx, info.filter, info.CountOptions)
}

// EstimatedCount 使用集合的元数据，执行计数命令，快速返回集合中文档的估计总数
func EstimatedCount(info *OperationInfo) (int64, error) {
	if err := initOperation(info); err != nil {
		return 0, err
	}
	return info.Collection.EstimatedDocumentCount(info.Ctx, info.EstimatedCountOptions)
}
